Proper connect_port
[juce-lv2.git] / juce / source / extras / the jucer / src / model / paintelements / jucer_PaintElementPath.cpp
blobd4560f7e4efc7d97aa1f4c10481dd49a696cf742
1 /*
2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../jucer_Headers.h"
27 #include "jucer_PaintElementPath.h"
28 #include "../../properties/jucer_PositionPropertyBase.h"
29 #include "jucer_PaintElementUndoableAction.h"
32 //==============================================================================
33 class ChangePointAction : public PaintElementUndoableAction <PaintElementPath>
35 public:
36 ChangePointAction (PathPoint* const point,
37 const int pointIndex,
38 const PathPoint& newValue_)
39 : PaintElementUndoableAction <PaintElementPath> (point->owner),
40 index (pointIndex),
41 newValue (newValue_),
42 oldValue (*point)
46 ChangePointAction (PathPoint* const point,
47 const PathPoint& newValue_)
48 : PaintElementUndoableAction <PaintElementPath> (point->owner),
49 index (point->owner->indexOfPoint (point)),
50 newValue (newValue_),
51 oldValue (*point)
55 bool perform()
57 return changeTo (newValue);
60 bool undo()
62 return changeTo (oldValue);
65 private:
66 const int index;
67 PathPoint newValue, oldValue;
69 PathPoint* getPoint() const
71 PathPoint* p = getElement()->getPoint (index);
72 jassert (p != 0);
73 return p;
76 bool changeTo (const PathPoint& value) const
78 showCorrectTab();
80 PaintElementPath* const path = getElement();
81 jassert (path != 0);
83 PathPoint* const p = path->getPoint (index);
84 jassert (p != 0);
86 const bool typeChanged = (p->type != value.type);
87 *p = value;
88 p->owner = path;
90 if (typeChanged)
91 path->pointListChanged();
93 path->changed();
94 return true;
99 //==============================================================================
100 class PathWindingModeProperty : public ChoicePropertyComponent,
101 public ChangeListener
103 public:
104 PathWindingModeProperty (PaintElementPath* const owner_)
105 : ChoicePropertyComponent ("winding rule"),
106 owner (owner_)
108 choices.add ("Non-zero winding");
109 choices.add ("Even/odd winding");
111 owner->getDocument()->addChangeListener (this);
114 ~PathWindingModeProperty()
116 owner->getDocument()->removeChangeListener (this);
119 //==============================================================================
120 void setIndex (int newIndex) { owner->setNonZeroWinding (newIndex == 0, true); }
121 int getIndex() const { return owner->isNonZeroWinding() ? 0 : 1; }
123 void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
125 private:
126 PaintElementPath* const owner;
130 //==============================================================================
131 PaintElementPath::PaintElementPath (PaintRoutine* owner)
132 : ColouredElement (owner, "Path", true, true),
133 nonZeroWinding (true)
137 PaintElementPath::~PaintElementPath()
141 //==============================================================================
142 static int randomPos (int size)
144 return size / 4 + Random::getSystemRandom().nextInt (size / 4) - size / 8;
147 void PaintElementPath::setInitialBounds (int w, int h)
149 String s;
151 int x = randomPos (w);
152 int y = randomPos (h);
154 s << "s "
155 << x << " " << y << " l "
156 << (x + 30) << " " << (y + 50) << " l "
157 << (x - 30) << " " << (y + 50) << " x";
159 restorePathFromString (s);
162 //==============================================================================
163 int PaintElementPath::getBorderSize() const
165 return isStrokePresent ? 1 + roundFloatToInt (strokeType.stroke.getStrokeThickness())
166 : 0;
169 const Rectangle<int> PaintElementPath::getCurrentBounds (const Rectangle<int>& parentArea) const
171 updateStoredPath (getDocument()->getComponentLayout(), parentArea);
173 Rectangle<float> bounds (path.getBounds());
175 const int borderSize = getBorderSize();
177 return Rectangle<int> ((int) bounds.getX() - borderSize,
178 (int) bounds.getY() - borderSize,
179 (int) bounds.getWidth() + borderSize * 2,
180 (int) bounds.getHeight() + borderSize * 2);
183 void PaintElementPath::setCurrentBounds (const Rectangle<int>& b,
184 const Rectangle<int>& parentArea,
185 const bool undoable)
187 Rectangle<int> newBounds (b);
188 newBounds.setSize (jmax (1, newBounds.getWidth()),
189 jmax (1, newBounds.getHeight()));
191 const Rectangle<int> current (getCurrentBounds (parentArea));
193 if (newBounds != current)
195 const int borderSize = getBorderSize();
197 const int dx = newBounds.getX() - current.getX();
198 const int dy = newBounds.getY() - current.getY();
200 const double scaleStartX = current.getX() + borderSize;
201 const double scaleStartY = current.getY() + borderSize;
202 const double scaleX = (newBounds.getWidth() - borderSize * 2) / (double) (current.getWidth() - borderSize * 2);
203 const double scaleY = (newBounds.getHeight() - borderSize * 2) / (double) (current.getHeight() - borderSize * 2);
205 for (int i = 0; i < points.size(); ++i)
207 PathPoint* const destPoint = points.getUnchecked(i);
208 PathPoint p (*destPoint);
210 for (int j = p.getNumPoints(); --j >= 0;)
211 rescalePoint (p.pos[j], dx, dy,
212 scaleX, scaleY,
213 scaleStartX, scaleStartY,
214 parentArea);
216 perform (new ChangePointAction (destPoint, i, p), "Move path");
221 void PaintElementPath::rescalePoint (RelativePositionedRectangle& pos, int dx, int dy,
222 double scaleX, double scaleY,
223 double scaleStartX, double scaleStartY,
224 const Rectangle<int>& parentArea) const
226 double x, y, w, h;
227 pos.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
229 x = (x - scaleStartX) * scaleX + scaleStartX + dx;
230 y = (y - scaleStartY) * scaleY + scaleStartY + dy;
232 pos.updateFrom (x, y, w, h, parentArea, getDocument()->getComponentLayout());
235 //==============================================================================
236 static void drawArrow (Graphics& g, float x1, float y1, float x2, float y2)
238 g.drawArrow (Line<float> (x1, y1, (x1 + x2) * 0.5f, (y1 + y2) * 0.5f), 1.0f, 8.0f, 10.0f);
239 g.drawLine (x1 + (x2 - x1) * 0.49f, y1 + (y2 - y1) * 0.49f, x2, y2);
242 void PaintElementPath::draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea)
244 updateStoredPath (layout, parentArea);
245 path.setUsingNonZeroWinding (nonZeroWinding);
247 fillType.setFillType (g, getDocument(), parentArea);
248 g.fillPath (path);
250 if (isStrokePresent)
252 strokeType.fill.setFillType (g, getDocument(), parentArea);
253 g.strokePath (path, getStrokeType().stroke);
257 void PaintElementPath::drawExtraEditorGraphics (Graphics& g, const Rectangle<int>& relativeTo)
259 ComponentLayout* layout = getDocument()->getComponentLayout();
261 for (int i = 0; i < points.size(); ++i)
263 PathPoint* const p = points.getUnchecked (i);
265 const int numPoints = p->getNumPoints();
267 if (numPoints > 0)
269 if (owner->getSelectedPoints().isSelected (p))
271 g.setColour (Colours::red);
272 double x1, y1, x2, y2;
274 if (numPoints > 2)
276 positionToXY (p->pos [1], x1, y1, relativeTo, layout);
277 positionToXY (p->pos [2], x2, y2, relativeTo, layout);
278 drawArrow (g, (float) x1, (float) y1, (float) x2, (float) y2);
281 if (numPoints > 1)
283 positionToXY (p->pos [0], x1, y1, relativeTo, layout);
284 positionToXY (p->pos [1], x2, y2, relativeTo, layout);
285 drawArrow (g, (float) x1, (float) y1, (float) x2, (float) y2);
288 positionToXY (p->pos [0], x2, y2, relativeTo, layout);
290 const PathPoint* const nextPoint = points [i - 1];
292 if (nextPoint != 0)
294 positionToXY (nextPoint->pos [nextPoint->getNumPoints() - 1], x1, y1, relativeTo, layout);
295 drawArrow (g, (float) x1, (float) y1, (float) x2, (float) y2);
302 void PaintElementPath::resized()
304 ColouredElement::resized();
307 void PaintElementPath::parentSizeChanged()
309 repaint();
312 //==============================================================================
313 void PaintElementPath::mouseDown (const MouseEvent& e)
315 if (e.mods.isPopupMenu() || ! owner->getSelectedElements().isSelected (this))
316 mouseDownOnSegment = -1;
317 else
318 mouseDownOnSegment = findSegmentAtXY (getX() + e.x, getY() + e.y);
320 if (points [mouseDownOnSegment] != 0)
322 mouseDownSelectSegmentStatus = owner->getSelectedPoints().addToSelectionOnMouseDown (points [mouseDownOnSegment], e.mods);
324 else
326 ColouredElement::mouseDown (e);
330 void PaintElementPath::mouseDrag (const MouseEvent& e)
332 if (mouseDownOnSegment < 0)
333 ColouredElement::mouseDrag (e);
336 void PaintElementPath::mouseUp (const MouseEvent& e)
338 if (points [mouseDownOnSegment] == 0)
340 ColouredElement::mouseUp (e);
342 else
344 owner->getSelectedPoints().addToSelectionOnMouseUp (points [mouseDownOnSegment],
345 e.mods, false, mouseDownSelectSegmentStatus);
349 //==============================================================================
350 void PaintElementPath::changed()
352 ColouredElement::changed();
353 lastPathBounds = Rectangle<int>();
356 void PaintElementPath::pointListChanged()
358 changed();
359 siblingComponentsChanged();
362 //==============================================================================
363 void PaintElementPath::getEditableProperties (Array <PropertyComponent*>& properties)
365 properties.add (new PathWindingModeProperty (this));
366 getColourSpecificProperties (properties);
369 //==============================================================================
370 static const String positionToPairOfValues (const RelativePositionedRectangle& position,
371 const ComponentLayout* layout)
373 String x, y, w, h;
374 positionToCode (position, layout, x, y, w, h);
375 return castToFloat (x) + ", " + castToFloat (y);
378 void PaintElementPath::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode)
380 if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
381 return;
383 const String pathVariable ("internalPath" + String (code.getUniqueSuffix()));
385 const ComponentLayout* layout = code.document->getComponentLayout();
387 code.privateMemberDeclarations
388 << "Path " << pathVariable << ";\n";
390 String r;
391 bool somePointsAreRelative = false;
393 if (! nonZeroWinding)
394 r << pathVariable << ".setUsingNonZeroWinding (false);\n";
396 for (int i = 0; i < points.size(); ++i)
398 const PathPoint* const p = points.getUnchecked(i);
400 switch (p->type)
402 case Path::Iterator::startNewSubPath:
403 r << pathVariable << ".startNewSubPath (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
404 somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
405 break;
407 case Path::Iterator::lineTo:
408 r << pathVariable << ".lineTo (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
409 somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
410 break;
412 case Path::Iterator::quadraticTo:
413 r << pathVariable << ".quadraticTo (" << positionToPairOfValues (p->pos[0], layout)
414 << ", " << positionToPairOfValues (p->pos[1], layout) << ");\n";
415 somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
416 somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
417 break;
419 case Path::Iterator::cubicTo:
420 r << pathVariable << ".cubicTo (" << positionToPairOfValues (p->pos[0], layout)
421 << ", " << positionToPairOfValues (p->pos[1], layout)
422 << ", " << positionToPairOfValues (p->pos[2], layout) << ");\n";
423 somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
424 somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
425 somePointsAreRelative = somePointsAreRelative || ! p->pos[2].rect.isPositionAbsolute();
426 break;
428 case Path::Iterator::closePath:
429 r << pathVariable << ".closeSubPath();\n";
430 break;
432 default:
433 jassertfalse
434 break;
438 r << '\n';
440 if (somePointsAreRelative)
441 code.getCallbackCode (String::empty, "void", "resized()", false)
442 << pathVariable << ".clear();\n" << r;
443 else
444 code.constructorCode << r;
446 if (! fillType.isInvisible())
448 fillType.fillInGeneratedCode (code, paintMethodCode);
450 paintMethodCode << "g.fillPath (" << pathVariable << ");\n";
453 if (isStrokePresent && ! strokeType.isInvisible())
455 String s;
457 strokeType.fill.fillInGeneratedCode (code, s);
458 s << "g.strokePath (" << pathVariable << ", " << strokeType.getPathStrokeCode() << ");\n";
460 paintMethodCode += s;
463 paintMethodCode += "\n";
466 //==============================================================================
467 XmlElement* PaintElementPath::createXml() const
469 XmlElement* e = new XmlElement (getTagName());
470 position.applyToXml (*e);
471 addColourAttributes (e);
472 e->setAttribute ("nonZeroWinding", nonZeroWinding);
474 e->addTextElement (pathToString());
476 return e;
479 bool PaintElementPath::loadFromXml (const XmlElement& xml)
481 if (xml.hasTagName (getTagName()))
483 position.restoreFromXml (xml, position);
484 loadColourAttributes (xml);
485 nonZeroWinding = xml.getBoolAttribute ("nonZeroWinding", true);
487 restorePathFromString (xml.getAllSubText().trim());
489 return true;
491 else
493 jassertfalse
494 return false;
498 //==============================================================================
499 void PaintElementPath::createSiblingComponents()
501 ColouredElement::createSiblingComponents();
503 int i;
504 for (i = 0; i < points.size(); ++i)
506 const PathPoint* const p = points.getUnchecked(i);
508 switch (p->type)
510 case Path::Iterator::startNewSubPath:
511 siblingComponents.add (new PathPointComponent (this, i, 0));
512 break;
513 case Path::Iterator::lineTo:
514 siblingComponents.add (new PathPointComponent (this, i, 0));
515 break;
516 case Path::Iterator::quadraticTo:
517 siblingComponents.add (new PathPointComponent (this, i, 0));
518 siblingComponents.add (new PathPointComponent (this, i, 1));
519 break;
520 case Path::Iterator::cubicTo:
521 siblingComponents.add (new PathPointComponent (this, i, 0));
522 siblingComponents.add (new PathPointComponent (this, i, 1));
523 siblingComponents.add (new PathPointComponent (this, i, 2));
524 break;
525 case Path::Iterator::closePath:
526 break;
527 default:
528 jassertfalse
529 break;
533 for (i = 0; i < siblingComponents.size(); ++i)
535 getParentComponent()->addAndMakeVisible (siblingComponents.getUnchecked(i));
536 siblingComponents.getUnchecked(i)->updatePosition();
541 const String PaintElementPath::pathToString() const
543 String s;
545 for (int i = 0; i < points.size(); ++i)
547 const PathPoint* const p = points.getUnchecked(i);
549 switch (p->type)
551 case Path::Iterator::startNewSubPath:
552 s << "s " << positionToString (p->pos [0]) << ' ';
553 break;
554 case Path::Iterator::lineTo:
555 s << "l " << positionToString (p->pos [0]) << ' ';
556 break;
557 case Path::Iterator::quadraticTo:
558 s << "q " << positionToString (p->pos [0])
559 << ' ' << positionToString (p->pos [1]) << ' ';
560 break;
561 case Path::Iterator::cubicTo:
562 s << "c " << positionToString (p->pos [0])
563 << ' ' << positionToString (p->pos [1]) << ' '
564 << ' ' << positionToString (p->pos [2]) << ' ';
565 break;
566 case Path::Iterator::closePath:
567 s << "x ";
568 break;
569 default:
570 jassertfalse
571 break;
575 return s.trimEnd();
578 void PaintElementPath::restorePathFromString (const String& s)
580 points.clear();
582 StringArray tokens;
583 tokens.addTokens (s, false);
584 tokens.trim();
585 tokens.removeEmptyStrings();
587 for (int i = 0; i < tokens.size(); ++i)
589 PathPoint* p = new PathPoint (this);
591 if (tokens[i] == "s")
593 p->type = Path::Iterator::startNewSubPath;
594 p->pos [0] = RelativePositionedRectangle();
595 p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
596 i += 2;
598 else if (tokens[i] == "l")
600 p->type = Path::Iterator::lineTo;
601 p->pos [0] = RelativePositionedRectangle();
602 p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
603 i += 2;
605 else if (tokens[i] == "q")
607 p->type = Path::Iterator::quadraticTo;
608 p->pos [0] = RelativePositionedRectangle();
609 p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
610 p->pos [1] = RelativePositionedRectangle();
611 p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
612 i += 4;
614 else if (tokens[i] == "c")
616 p->type = Path::Iterator::cubicTo;
617 p->pos [0] = RelativePositionedRectangle();
618 p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
619 p->pos [1] = RelativePositionedRectangle();
620 p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
621 p->pos [2] = RelativePositionedRectangle();
622 p->pos [2].rect = PositionedRectangle (tokens [i + 5] + " " + tokens [i + 6]);
623 i += 6;
625 else if (tokens[i] == "x")
627 p->type = Path::Iterator::closePath;
629 else
631 delete p;
632 continue;
635 points.add (p);
639 void PaintElementPath::setToPath (const Path& p)
641 points.clear();
643 Path::Iterator i (p);
645 while (i.next())
647 PathPoint* p = new PathPoint (this);
648 p->type = i.elementType;
650 if (i.elementType == Path::Iterator::startNewSubPath)
652 p->pos [0].rect.setX (i.x1);
653 p->pos [0].rect.setY (i.y1);
655 else if (i.elementType == Path::Iterator::lineTo)
657 p->pos [0].rect.setX (i.x1);
658 p->pos [0].rect.setY (i.y1);
660 else if (i.elementType == Path::Iterator::quadraticTo)
662 p->pos [0].rect.setX (i.x1);
663 p->pos [0].rect.setY (i.y1);
664 p->pos [1].rect.setX (i.x2);
665 p->pos [1].rect.setY (i.y2);
667 else if (i.elementType == Path::Iterator::cubicTo)
669 p->pos [0].rect.setX (i.x1);
670 p->pos [0].rect.setY (i.y1);
671 p->pos [1].rect.setX (i.x2);
672 p->pos [1].rect.setY (i.y2);
673 p->pos [2].rect.setX (i.x3);
674 p->pos [2].rect.setY (i.y3);
676 else if (i.elementType == Path::Iterator::closePath)
679 else
681 delete p;
682 continue;
685 points.add (p);
689 void PaintElementPath::updateStoredPath (const ComponentLayout* layout, const Rectangle<int>& relativeTo) const
691 if (lastPathBounds != relativeTo && ! relativeTo.isEmpty())
693 lastPathBounds = relativeTo;
695 path.clear();
697 for (int i = 0; i < points.size(); ++i)
699 const PathPoint* const p = points.getUnchecked(i);
700 double x1, y1, x2, y2, x3, y3;
702 switch (p->type)
704 case Path::Iterator::startNewSubPath:
705 positionToXY (p->pos [0], x1, y1, relativeTo, layout);
706 path.startNewSubPath ((float) x1, (float) y1);
707 break;
709 case Path::Iterator::lineTo:
710 positionToXY (p->pos [0], x1, y1, relativeTo, layout);
711 path.lineTo ((float) x1, (float) y1);
712 break;
714 case Path::Iterator::quadraticTo:
715 positionToXY (p->pos [0], x1, y1, relativeTo, layout);
716 positionToXY (p->pos [1], x2, y2, relativeTo, layout);
717 path.quadraticTo ((float) x1, (float) y1, (float) x2, (float) y2);
718 break;
720 case Path::Iterator::cubicTo:
721 positionToXY (p->pos [0], x1, y1, relativeTo, layout);
722 positionToXY (p->pos [1], x2, y2, relativeTo, layout);
723 positionToXY (p->pos [2], x3, y3, relativeTo, layout);
724 path.cubicTo ((float) x1, (float) y1, (float) x2, (float) y2, (float) x3, (float) y3);
725 break;
727 case Path::Iterator::closePath:
728 path.closeSubPath();
729 break;
731 default:
732 jassertfalse
733 break;
739 //==============================================================================
740 class ChangeWindingAction : public PaintElementUndoableAction <PaintElementPath>
742 public:
743 ChangeWindingAction (PaintElementPath* const path, const bool newValue_)
744 : PaintElementUndoableAction <PaintElementPath> (path),
745 newValue (newValue_),
746 oldValue (path->isNonZeroWinding())
750 bool perform()
752 showCorrectTab();
753 getElement()->setNonZeroWinding (newValue, false);
754 return true;
757 bool undo()
759 showCorrectTab();
760 getElement()->setNonZeroWinding (oldValue, false);
761 return true;
764 private:
765 bool newValue, oldValue;
768 void PaintElementPath::setNonZeroWinding (const bool nonZero, const bool undoable)
770 if (nonZero != nonZeroWinding)
772 if (undoable)
774 perform (new ChangeWindingAction (this, nonZero), "Change path winding rule");
776 else
778 nonZeroWinding = nonZero;
779 changed();
784 bool PaintElementPath::isSubpathClosed (int index) const
786 for (int i = index + 1; i < points.size(); ++i)
788 if (points.getUnchecked (i)->type == Path::Iterator::closePath)
789 return true;
790 else if (points.getUnchecked (i)->type == Path::Iterator::startNewSubPath)
791 break;
794 return false;
797 //==============================================================================
798 void PaintElementPath::setSubpathClosed (int index, const bool closed, const bool undoable)
800 if (closed != isSubpathClosed (index))
802 for (int i = index + 1; i < points.size(); ++i)
804 PathPoint* p = points.getUnchecked (i);
806 if (p->type == Path::Iterator::closePath)
808 jassert (! closed);
810 deletePoint (i, undoable);
811 return;
813 else if (p->type == Path::Iterator::startNewSubPath)
815 jassert (closed);
817 PathPoint* p = addPoint (i - 1, undoable);
819 PathPoint p2 (*p);
820 p2.type = Path::Iterator::closePath;
821 perform (new ChangePointAction (p, p2), "Close subpath");
822 return;
826 jassert (closed);
828 PathPoint* p = addPoint (points.size() - 1, undoable);
829 PathPoint p2 (*p);
830 p2.type = Path::Iterator::closePath;
831 perform (new ChangePointAction (p, p2), "Close subpath");
835 //==============================================================================
836 class AddPointAction : public PaintElementUndoableAction <PaintElementPath>
838 public:
839 AddPointAction (PaintElementPath* path, int pointIndexToAddItAfter_)
840 : PaintElementUndoableAction <PaintElementPath> (path),
841 indexAdded (-1),
842 pointIndexToAddItAfter (pointIndexToAddItAfter_)
846 bool perform()
848 showCorrectTab();
850 PaintElementPath* const path = getElement();
851 jassert (path != 0);
853 PathPoint* const p = path->addPoint (pointIndexToAddItAfter, false);
854 jassert (p != 0);
856 indexAdded = path->indexOfPoint (p);
857 jassert (indexAdded >= 0);
858 return true;
861 bool undo()
863 showCorrectTab();
865 PaintElementPath* const path = getElement();
866 jassert (path != 0);
868 path->deletePoint (indexAdded, false);
869 return true;
872 int indexAdded;
874 private:
875 int pointIndexToAddItAfter;
878 PathPoint* PaintElementPath::addPoint (int pointIndexToAddItAfter, const bool undoable)
880 if (undoable)
882 AddPointAction* action = new AddPointAction (this, pointIndexToAddItAfter);
883 perform (action, "Add path point");
884 return points [action->indexAdded];
886 else
888 double x1 = 20.0, y1 = 20.0, x2, y2;
890 ComponentLayout* layout = getDocument()->getComponentLayout();
891 const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
893 if (points [pointIndexToAddItAfter] != 0)
894 positionToXY (points [pointIndexToAddItAfter]->pos [points [pointIndexToAddItAfter]->getNumPoints() - 1], x1, y1,
895 area, layout);
896 else if (points[0] != 0)
897 positionToXY (points[0]->pos [0], x1, y1,
898 area, layout);
900 x2 = x1 + 50.0;
901 y2 = y1 + 50.0;
903 if (points [pointIndexToAddItAfter + 1] != 0)
905 if (points [pointIndexToAddItAfter + 1]->type == Path::Iterator::closePath
906 || points [pointIndexToAddItAfter + 1]->type == Path::Iterator::startNewSubPath)
908 int i = pointIndexToAddItAfter;
909 while (i > 0)
910 if (points [--i]->type == Path::Iterator::startNewSubPath)
911 break;
913 if (i != pointIndexToAddItAfter)
914 positionToXY (points [i]->pos [0], x2, y2,
915 area, layout);
917 else
919 positionToXY (points [pointIndexToAddItAfter + 1]->pos [0], x2, y2,
920 area, layout);
923 else
925 int i = pointIndexToAddItAfter + 1;
926 while (i > 0)
927 if (points [--i]->type == Path::Iterator::startNewSubPath)
928 break;
930 positionToXY (points [i]->pos [0], x2, y2,
931 area, layout);
934 PathPoint* const p = new PathPoint (this);
936 p->type = Path::Iterator::lineTo;
937 p->pos[0].rect.setX ((x1 + x2) * 0.5f);
938 p->pos[0].rect.setY ((y1 + y2) * 0.5f);
940 points.insert (pointIndexToAddItAfter + 1, p);
942 pointListChanged();
944 return p;
948 //==============================================================================
949 class DeletePointAction : public PaintElementUndoableAction <PaintElementPath>
951 public:
952 DeletePointAction (PaintElementPath* const path, const int indexToRemove_)
953 : PaintElementUndoableAction <PaintElementPath> (path),
954 indexToRemove (indexToRemove_),
955 oldValue (*path->getPoint (indexToRemove))
959 bool perform()
961 showCorrectTab();
963 PaintElementPath* const path = getElement();
964 jassert (path != 0);
966 path->deletePoint (indexToRemove, false);
967 return path != 0;
970 bool undo()
972 showCorrectTab();
974 PaintElementPath* const path = getElement();
975 jassert (path != 0);
977 PathPoint* p = path->addPoint (indexToRemove - 1, false);
978 *p = oldValue;
980 return path != 0;
983 int indexToRemove;
985 private:
986 PathPoint oldValue;
989 void PaintElementPath::deletePoint (int pointIndex, const bool undoable)
991 if (undoable)
993 perform (new DeletePointAction (this, pointIndex), "Delete path point");
995 else
997 PathPoint* const p = points [pointIndex];
999 if (p != 0 && pointIndex > 0)
1001 owner->getSelectedPoints().deselect (p);
1002 owner->getSelectedPoints().changed (true);
1004 points.remove (pointIndex);
1005 pointListChanged();
1010 //==============================================================================
1011 bool PaintElementPath::getPoint (int index, int pointNumber, double& x, double& y, const Rectangle<int>& parentArea) const
1013 const PathPoint* const p = points [index];
1015 if (p == 0)
1016 return false;
1018 jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
1019 jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
1021 positionToXY (p->pos [pointNumber], x, y, parentArea, getDocument()->getComponentLayout());
1022 return true;
1025 int PaintElementPath::findSegmentAtXY (int x, int y) const
1027 double x1, y1, x2, y2, x3, y3, lastX = 0.0, lastY = 0.0, subPathStartX = 0.0, subPathStartY = 0.0;
1029 ComponentLayout* const layout = getDocument()->getComponentLayout();
1030 const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
1032 int subpathStartIndex = 0;
1034 float thickness = 10.0f;
1035 if (isStrokePresent)
1036 thickness = jmax (thickness, strokeType.stroke.getStrokeThickness());
1038 for (int i = 0; i < points.size(); ++i)
1040 Path segmentPath;
1041 PathPoint* const p = points.getUnchecked (i);
1043 switch (p->type)
1045 case Path::Iterator::startNewSubPath:
1046 positionToXY (p->pos [0], lastX, lastY, area, layout);
1047 subPathStartX = lastX;
1048 subPathStartY = lastY;
1049 subpathStartIndex = i;
1050 break;
1052 case Path::Iterator::lineTo:
1053 positionToXY (p->pos [0], x1, y1, area, layout);
1055 segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) x1, (float) y1), thickness);
1056 if (segmentPath.contains ((float) x, (float) y))
1057 return i;
1059 lastX = x1;
1060 lastY = y1;
1061 break;
1063 case Path::Iterator::quadraticTo:
1064 positionToXY (p->pos [0], x1, y1, area, layout);
1065 positionToXY (p->pos [1], x2, y2, area, layout);
1067 segmentPath.startNewSubPath ((float) lastX, (float) lastY);
1068 segmentPath.quadraticTo ((float) x1, (float) y1, (float) x2, (float) y2);
1069 PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
1071 if (segmentPath.contains ((float) x, (float) y))
1072 return i;
1074 lastX = x2;
1075 lastY = y2;
1076 break;
1078 case Path::Iterator::cubicTo:
1079 positionToXY (p->pos [0], x1, y1, area, layout);
1080 positionToXY (p->pos [1], x2, y2, area, layout);
1081 positionToXY (p->pos [2], x3, y3, area, layout);
1083 segmentPath.startNewSubPath ((float) lastX, (float) lastY);
1084 segmentPath.cubicTo ((float) x1, (float) y1, (float) x2, (float) y2, (float) x3, (float) y3);
1085 PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
1087 if (segmentPath.contains ((float) x, (float) y))
1088 return i;
1090 lastX = x3;
1091 lastY = y3;
1092 break;
1094 case Path::Iterator::closePath:
1095 segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) subPathStartX, (float) subPathStartY), thickness);
1096 if (segmentPath.contains ((float) x, (float) y))
1097 return subpathStartIndex;
1099 lastX = subPathStartX;
1100 lastY = subPathStartY;
1101 break;
1103 default:
1104 jassertfalse
1105 break;
1109 return -1;
1112 //==============================================================================
1113 void PaintElementPath::movePoint (int index, int pointNumber,
1114 double newX, double newY,
1115 const Rectangle<int>& parentArea,
1116 const bool undoable)
1118 PathPoint* const p = points [index];
1120 if (p != 0)
1122 PathPoint newPoint (*p);
1123 jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
1124 jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
1126 RelativePositionedRectangle& pr = newPoint.pos [pointNumber];
1128 double x, y, w, h;
1129 pr.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
1130 pr.updateFrom (newX, newY, w, h, parentArea, getDocument()->getComponentLayout());
1132 if (undoable)
1134 perform (new ChangePointAction (p, index, newPoint), "Move path point");
1136 else
1138 *p = newPoint;
1139 changed();
1144 const RelativePositionedRectangle PaintElementPath::getPoint (int index, int pointNumber) const
1146 PathPoint* const p = points [index];
1148 if (p != 0)
1150 jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
1151 jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
1153 return p->pos [pointNumber];
1156 jassertfalse
1157 return RelativePositionedRectangle();
1160 void PaintElementPath::setPoint (int index, int pointNumber, const RelativePositionedRectangle& newPos, const bool undoable)
1162 PathPoint* const p = points [index];
1164 if (p != 0)
1166 PathPoint newPoint (*p);
1168 jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
1169 jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
1171 if (newPoint.pos [pointNumber] != newPos)
1173 newPoint.pos [pointNumber] = newPos;
1175 if (undoable)
1177 perform (new ChangePointAction (p, index, newPoint), "Change path point position");
1179 else
1181 *p = newPoint;
1182 changed();
1186 else
1188 jassertfalse
1192 //==============================================================================
1193 class PathPointTypeProperty : public ChoicePropertyComponent,
1194 public ChangeListener
1196 public:
1197 PathPointTypeProperty (PaintElementPath* const owner_,
1198 const int index_)
1199 : ChoicePropertyComponent ("point type"),
1200 owner (owner_),
1201 index (index_)
1203 choices.add ("Start of sub-path");
1204 choices.add ("Line");
1205 choices.add ("Quadratic");
1206 choices.add ("Cubic");
1208 owner->getDocument()->addChangeListener (this);
1211 ~PathPointTypeProperty()
1213 owner->getDocument()->removeChangeListener (this);
1216 //==============================================================================
1217 void setIndex (int newIndex)
1219 Path::Iterator::PathElementType type = Path::Iterator::startNewSubPath;
1221 switch (newIndex)
1223 case 0:
1224 type = Path::Iterator::startNewSubPath;
1225 break;
1227 case 1:
1228 type = Path::Iterator::lineTo;
1229 break;
1231 case 2:
1232 type = Path::Iterator::quadraticTo;
1233 break;
1235 case 3:
1236 type = Path::Iterator::cubicTo;
1237 break;
1239 default:
1240 jassertfalse
1241 break;
1244 const Rectangle<int> area (((PaintRoutineEditor*) owner->getParentComponent())->getComponentArea());
1246 owner->getPoint (index)->changePointType (type, area, true);
1249 int getIndex() const
1251 const PathPoint* const p = owner->getPoint (index);
1252 jassert (p != 0);
1254 switch (p->type)
1256 case Path::Iterator::startNewSubPath:
1257 return 0;
1259 case Path::Iterator::lineTo:
1260 return 1;
1262 case Path::Iterator::quadraticTo:
1263 return 2;
1265 case Path::Iterator::cubicTo:
1266 return 3;
1268 case Path::Iterator::closePath:
1269 break;
1271 default:
1272 jassertfalse
1273 break;
1276 return 0;
1279 void changeListenerCallback (ChangeBroadcaster*)
1281 refresh();
1284 private:
1285 PaintElementPath* const owner;
1286 const int index;
1289 //==============================================================================
1290 class PathPointPositionProperty : public PositionPropertyBase
1292 public:
1293 PathPointPositionProperty (PaintElementPath* const owner_,
1294 const int index_, const int pointNumber_,
1295 const String& name,
1296 ComponentPositionDimension dimension_)
1297 : PositionPropertyBase (owner_, name, dimension_, false, false,
1298 owner_->getDocument()->getComponentLayout()),
1299 owner (owner_),
1300 index (index_),
1301 pointNumber (pointNumber_)
1303 owner->getDocument()->addChangeListener (this);
1306 ~PathPointPositionProperty()
1308 owner->getDocument()->removeChangeListener (this);
1311 //==============================================================================
1312 void setPosition (const RelativePositionedRectangle& newPos)
1314 owner->setPoint (index, pointNumber, newPos, true);
1317 const RelativePositionedRectangle getPosition() const
1319 return owner->getPoint (index, pointNumber);
1322 private:
1323 PaintElementPath* const owner;
1324 const int index, pointNumber;
1327 //==============================================================================
1328 class PathPointClosedProperty : public ChoicePropertyComponent,
1329 private ChangeListener
1331 public:
1332 PathPointClosedProperty (PaintElementPath* const owner_, const int index_)
1333 : ChoicePropertyComponent ("openness"),
1334 owner (owner_),
1335 index (index_)
1337 owner->getDocument()->addChangeListener (this);
1339 choices.add ("Subpath is closed");
1340 choices.add ("Subpath is open-ended");
1343 ~PathPointClosedProperty()
1345 owner->getDocument()->removeChangeListener (this);
1348 void changeListenerCallback (ChangeBroadcaster*)
1350 refresh();
1353 void setIndex (int newIndex)
1355 owner->setSubpathClosed (index, newIndex == 0, true);
1358 int getIndex() const
1360 return owner->isSubpathClosed (index) ? 0 : 1;
1363 private:
1364 PaintElementPath* const owner;
1365 const int index;
1368 //==============================================================================
1369 class AddNewPointProperty : public ButtonPropertyComponent
1371 public:
1372 AddNewPointProperty (PaintElementPath* const owner_, const int index_)
1373 : ButtonPropertyComponent ("new point", false),
1374 owner (owner_),
1375 index (index_)
1379 ~AddNewPointProperty() {}
1381 void buttonClicked()
1383 owner->addPoint (index, true);
1386 const String getButtonText() const { return "Add new point"; }
1388 private:
1389 PaintElementPath* const owner;
1390 const int index;
1394 //==============================================================================
1395 PathPoint::PathPoint (PaintElementPath* const owner_)
1396 : owner (owner_)
1400 PathPoint::PathPoint (const PathPoint& other)
1401 : owner (other.owner),
1402 type (other.type)
1404 pos [0] = other.pos [0];
1405 pos [1] = other.pos [1];
1406 pos [2] = other.pos [2];
1409 PathPoint& PathPoint::operator= (const PathPoint& other)
1411 owner = other.owner;
1412 type = other.type;
1413 pos [0] = other.pos [0];
1414 pos [1] = other.pos [1];
1415 pos [2] = other.pos [2];
1416 return *this;
1419 PathPoint::~PathPoint()
1423 int PathPoint::getNumPoints() const
1425 if (type == Path::Iterator::cubicTo)
1426 return 3;
1428 if (type == Path::Iterator::quadraticTo)
1429 return 2;
1431 if (type == Path::Iterator::closePath)
1432 return 0;
1434 return 1;
1437 const PathPoint PathPoint::withChangedPointType (const Path::Iterator::PathElementType newType,
1438 const Rectangle<int>& parentArea) const
1440 PathPoint p (*this);
1442 if (newType != p.type)
1444 int oldNumPoints = getNumPoints();
1445 p.type = newType;
1446 int numPoints = p.getNumPoints();
1448 if (numPoints != oldNumPoints)
1450 double lastX, lastY;
1451 double x, y, w, h;
1453 p.pos [numPoints - 1] = p.pos [oldNumPoints - 1];
1454 p.pos [numPoints - 1].getRectangleDouble (x, y, w, h, parentArea, owner->getDocument()->getComponentLayout());
1456 const int index = owner->points.indexOf (this);
1457 PathPoint* lastPoint = owner->points [index - 1];
1459 jassert (lastPoint != 0)
1460 if (lastPoint != 0)
1462 lastPoint->pos [lastPoint->getNumPoints() - 1]
1463 .getRectangleDouble (lastX, lastY, w, h, parentArea, owner->getDocument()->getComponentLayout());
1465 else
1467 lastX = x;
1468 lastY = y;
1471 for (int i = 0; i < numPoints - 1; ++i)
1473 p.pos[i] = p.pos [numPoints - 1];
1475 p.pos[i].updateFrom (lastX + (x - lastX) * (i + 1) / numPoints,
1476 lastY + (y - lastY) * (i + 1) / numPoints,
1477 w, h,
1478 parentArea,
1479 owner->getDocument()->getComponentLayout());
1484 return p;
1487 void PathPoint::changePointType (const Path::Iterator::PathElementType newType,
1488 const Rectangle<int>& parentArea, const bool undoable)
1490 if (newType != type)
1492 if (undoable)
1494 owner->perform (new ChangePointAction (this, withChangedPointType (newType, parentArea)),
1495 "Change path point type");
1497 else
1499 *this = withChangedPointType (newType, parentArea);
1500 owner->pointListChanged();
1505 void PathPoint::getEditableProperties (Array <PropertyComponent*>& properties)
1507 const int index = owner->points.indexOf (this);
1508 jassert (index >= 0);
1510 switch (type)
1512 case Path::Iterator::startNewSubPath:
1513 properties.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
1514 properties.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
1516 properties.add (new PathPointClosedProperty (owner, index));
1517 properties.add (new AddNewPointProperty (owner, index));
1518 break;
1520 case Path::Iterator::lineTo:
1521 properties.add (new PathPointTypeProperty (owner, index));
1522 properties.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
1523 properties.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
1524 properties.add (new AddNewPointProperty (owner, index));
1525 break;
1527 case Path::Iterator::quadraticTo:
1528 properties.add (new PathPointTypeProperty (owner, index));
1529 properties.add (new PathPointPositionProperty (owner, index, 0, "control pt x", PositionPropertyBase::componentX));
1530 properties.add (new PathPointPositionProperty (owner, index, 0, "control pt y", PositionPropertyBase::componentY));
1531 properties.add (new PathPointPositionProperty (owner, index, 1, "x", PositionPropertyBase::componentX));
1532 properties.add (new PathPointPositionProperty (owner, index, 1, "y", PositionPropertyBase::componentY));
1533 properties.add (new AddNewPointProperty (owner, index));
1534 break;
1536 case Path::Iterator::cubicTo:
1537 properties.add (new PathPointTypeProperty (owner, index));
1538 properties.add (new PathPointPositionProperty (owner, index, 0, "control pt1 x", PositionPropertyBase::componentX));
1539 properties.add (new PathPointPositionProperty (owner, index, 0, "control pt1 y", PositionPropertyBase::componentY));
1540 properties.add (new PathPointPositionProperty (owner, index, 1, "control pt2 x", PositionPropertyBase::componentX));
1541 properties.add (new PathPointPositionProperty (owner, index, 1, "control pt2 y", PositionPropertyBase::componentY));
1542 properties.add (new PathPointPositionProperty (owner, index, 2, "x", PositionPropertyBase::componentX));
1543 properties.add (new PathPointPositionProperty (owner, index, 2, "y", PositionPropertyBase::componentY));
1544 properties.add (new AddNewPointProperty (owner, index));
1545 break;
1547 case Path::Iterator::closePath:
1548 break;
1550 default:
1551 jassertfalse
1552 break;
1556 void PathPoint::deleteFromPath()
1558 owner->deletePoint (owner->points.indexOf (this), true);
1561 //==============================================================================
1562 PathPointComponent::PathPointComponent (PaintElementPath* const path_,
1563 const int index_,
1564 const int pointNumber_)
1565 : ElementSiblingComponent (path_),
1566 path (path_),
1567 routine (path_->getOwner()),
1568 index (index_),
1569 pointNumber (pointNumber_),
1570 selected (false)
1572 setSize (11, 11);
1573 setRepaintsOnMouseActivity (true);
1575 selected = routine->getSelectedPoints().isSelected (path_->points [index]);
1576 routine->getSelectedPoints().addChangeListener (this);
1579 PathPointComponent::~PathPointComponent()
1581 routine->getSelectedPoints().removeChangeListener (this);
1584 void PathPointComponent::updatePosition()
1586 const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
1587 jassert (getParentComponent() != 0);
1589 double x, y;
1590 path->getPoint (index, pointNumber, x, y, area);
1592 setCentrePosition (roundToInt (x),
1593 roundToInt (y));
1596 void PathPointComponent::showPopupMenu()
1600 void PathPointComponent::paint (Graphics& g)
1602 if (isMouseOverOrDragging())
1603 g.fillAll (Colours::red);
1605 if (selected)
1607 g.setColour (Colours::red);
1608 g.drawRect (0, 0, getWidth(), getHeight());
1611 g.setColour (Colours::white);
1612 g.fillRect (getWidth() / 2 - 3, getHeight() / 2 - 3, 7, 7);
1614 g.setColour (Colours::black);
1616 if (pointNumber < path->getPoint (index)->getNumPoints() - 1)
1617 g.drawRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
1618 else
1619 g.fillRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
1622 void PathPointComponent::mouseDown (const MouseEvent& e)
1624 dragging = false;
1626 if (e.mods.isPopupMenu())
1628 showPopupMenu();
1629 return; // this may be deleted now..
1632 dragX = getX() + getWidth() / 2;
1633 dragY = getY() + getHeight() / 2;
1635 mouseDownSelectStatus = routine->getSelectedPoints().addToSelectionOnMouseDown (path->points [index], e.mods);
1637 owner->getDocument()->getUndoManager().beginNewTransaction();
1640 void PathPointComponent::mouseDrag (const MouseEvent& e)
1642 if (! e.mods.isPopupMenu())
1644 if (selected && ! dragging)
1645 dragging = ! e.mouseWasClicked();
1647 if (dragging)
1649 const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
1650 int x = dragX + e.getDistanceFromDragStartX() - area.getX();
1651 int y = dragY + e.getDistanceFromDragStartY() - area.getY();
1653 JucerDocument* const document = owner->getDocument();
1655 if (document != 0)
1657 x = document->snapPosition (x);
1658 y = document->snapPosition (y);
1661 owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
1662 path->movePoint (index, pointNumber, x + area.getX(), y + area.getY(), area, true);
1667 void PathPointComponent::mouseUp (const MouseEvent& e)
1669 routine->getSelectedPoints().addToSelectionOnMouseUp (path->points [index],
1670 e.mods, dragging,
1671 mouseDownSelectStatus);
1674 void PathPointComponent::changeListenerCallback (ChangeBroadcaster* source)
1676 ElementSiblingComponent::changeListenerCallback (source);
1678 const bool nowSelected = routine->getSelectedPoints().isSelected (path->points [index]);
1680 if (nowSelected != selected)
1682 selected = nowSelected;
1683 repaint();
1685 if (getParentComponent() != 0)
1686 getParentComponent()->repaint();